package interfaces.spawnMenu.spawnMap;

import interfaces.spawnMenu.AbsSpawnMenu;
import interfaces.spawnMenu.SpawnMenuWindow;
import interfaces.spawnMenu.spawnMap.SpawnButtonContent.SpawnButton;
import interfaces.spawnMenu.spawnMap.SpawnButtonContent.SpawnType;
import interfaces.superWidgets.UpdateWidget;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import logic.common.Squad;
import logic.common.game.Game;
import logic.common.player.Player;
import logic.common.player.playerListeners.HunterChangedEvent;
import logic.common.player.playerListeners.KillAssistEvent;
import logic.common.player.playerListeners.KillEvent;
import logic.common.player.playerListeners.MissionAssistEvent;
import logic.common.player.playerListeners.PlayerListener;
import logic.common.player.playerListeners.PlayerSquadListener;
import logic.common.player.playerListeners.PlayerValueListener;
import logic.common.player.playerListeners.RespawnEvent;
import logic.common.player.playerListeners.TargetChangedEvent;
import logic.common.team.Team;
import logic.common.team.teamListeners.DroneKillEvent;
import logic.common.team.teamListeners.ShipAddedEvent;
import logic.common.team.teamListeners.ShipRemovedEvent;
import logic.common.team.teamListeners.TeamListener;
import logic.common.team.teamListeners.TeamShipListener;
import logic.common.team.teamListeners.TeamValueChangedEvent;
import logic.nodes.DestructionEvent;
import logic.nodes.DestructionListener;
import logic.nodes.collision.CollidableNode;
import logic.nodes.collision.visibility.VisibilityChangedEvent;
import logic.nodes.collision.visibility.VisibilityListener;
import logic.ships.hunter.Hunter;
import logic.ships.moveableShip.MovableShip;
import logic.status.StatusChangedEvent;
import map.asteroids.AsteroidField;
import map.spawnStation.SpawnPosition;
import map.spawnStation.SpawnStation;
import networking.messages.syncMessages.ValueType;

import org.fenggui.decorator.background.PlainBackground;
import org.fenggui.event.ButtonPressedEvent;
import org.fenggui.event.IButtonPressedListener;
import org.fenggui.layout.StaticLayout;
import org.fenggui.util.Color;

import com.jme.math.Vector2f;
import com.jme.math.Vector3f;

public class SpawnMap extends SpawnMenuWindow implements UpdateWidget {
	
	private SpawnButtonContent selectedButton;
	private Game game;
	private HashMap<SpawnPosition, SpawnButtonContent> spawnButtons;
	private HashMap<MovableShip, MiniMapIcon> shipIcons;
	private HashMap<MovableShip, DestructionListener> destructionListeners;
	private HashMap<MovableShip, VisibilityListener> visListeners;
	private HashMap<Team, TeamListener> teamListeners;
	private HashMap<Team, TeamShipListener> teamShipListeners;
	private HashMap<Player, PlayerListener> playerlisteners;
	private HashMap<Player, PlayerValueListener> playerValuelisteners;
	private Set<MovableShip> visibleShips;
	private List<AsteroidIcon> asteroidIcons;
	private int asteroidIconSize;
	
	public SpawnMap(Game game, Team team, Team enemyTeam, AbsSpawnMenu spawnMenu) {
		super(null, team, enemyTeam, spawnMenu, false);
		this.game = game;
		
		spawnButtons = new HashMap<SpawnPosition, SpawnButtonContent>();
		shipIcons = new HashMap<MovableShip, MiniMapIcon>();
		visibleShips = new HashSet<MovableShip>();
		
		destructionListeners = new HashMap<MovableShip, DestructionListener>();
		visListeners = new HashMap<MovableShip, VisibilityListener>();
		teamListeners = new HashMap<Team, TeamListener>();
		teamShipListeners = new HashMap<Team, TeamShipListener>();
		playerlisteners = new HashMap<Player, PlayerListener>();
		playerValuelisteners = new HashMap<Player, PlayerValueListener>();
		
		content.setLayoutManager(new StaticLayout());
		content.getAppearance().add(new PlainBackground(Color.BLACK));
		
		asteroidIconSize = getHeight() / 36;
		
		setAsteroidIcons();
		initSpawnStations();
		initTeamSpawnPositions();
		initShips();
		
		initDefaultSpawn(false);
		
		initPilotListeners();
		initPlayerListener();
		
		Player player = game.getCurrentPlayer();
		Squad squad = player.getSquad();
		if(squad != null) {
			setupSquad(squad);
			SpawnPosition pos = player.getCurrentSpawnPosition();
			if(pos != null && pos instanceof Hunter) setSpawn(spawnButtons.get(pos));
		}
		
		layout();
	}
	
	private void initDefaultSpawn(boolean teamChanged) {
		SpawnPosition spawnPos = game.getCurrentPlayer().getCurrentSpawnPosition();
		if(!teamChanged && spawnPos != null) setSpawn(spawnButtons.get(spawnPos));
		else setSpawn(spawnButtons.get(team.getSpawnPositions().get(0)));
	}
	
	private void initPlayerListener() {
		spawnMenu.addListener(game.getCurrentPlayer(), new PlayerSquadListener() {
			@Override
			public void joinedSquad(Squad squad) {
				setupSquad(squad);
			}
			
			@Override
			public void leftSquad(Squad squad) {
				for(Player member : squad.getMembers()) {
					Hunter hunter = member.getHunter();
					
					if(hunter != null) {
						MiniMapIcon icon = shipIcons.remove(hunter);
						content.removeWidget(icon);
						addShip(hunter);
					}
				}
				
				if(selectedButton != null && selectedButton.getType() == SpawnType.Squad) setSpawn(null);
			}
		});
	}
	
	private void setupSquad(Squad squad) {
		for(Player member : squad.getMembers()) {
			Hunter hunter = member.getHunter();
			if(game.getCurrentPlayer() == member || hunter == null) continue;
			
			MiniMapIcon icon = shipIcons.remove(hunter);
			if(icon != null) content.removeWidget(icon);
			
			shipIcons.put(hunter, getShipIcon(hunter, true));
			changeVisiblity(hunter, true);
		}
	}
	
	private MiniMapIcon getShipIcon(MovableShip ship) {
		return getShipIcon(ship, false);
	}
	
	private MiniMapIcon getShipIcon(MovableShip ship, boolean isSquad) {
		int[] coords = getMapCoordinates(ship.getLocalTranslation());
		int x = coords[0];
		int y = coords[1];
		MiniMapIcon icon = null;
		
		if(isSquad) {
			Hunter hunter = (Hunter)ship;
			SpawnButtonContent button = new SpawnButtonContent(hunter, team, spawnMenu);
			button.addButtonPressedListener(getSpawnButtonListener());
			icon = button;
		} else icon = new ShipMapIcon(ship, game.getCurrentPlayer(), x, y);
		
		return icon;
	}
	
	private void initTeamSpawnPositions() {
		for(SpawnPosition spawnPos : team.getSpawnPositions()) {
			addSpawnPosition(spawnPos);
		}
	}
	
	private void initShips() {
		initShips(team.getCarriers());
		initShips(team.getFrigates());

		initShips(enemyTeam.getCarriers());
		initShips(enemyTeam.getFrigates());
	}
	
	private void initShips(Collection<? extends MovableShip> ships) {
		for(MovableShip ship : ships) {
			addShip(ship);
		}
	}
	
	private void initSpawnStations() {
		for(SpawnStation spawnStation : game.getMap().getSpawnStations()) {
			addSpawnPosition(spawnStation);
		}
	}
	
	private void addSpawnPosition(SpawnPosition pos) {
		SpawnButtonContent spawnButton = new SpawnButtonContent(pos, team, spawnMenu);
		spawnButton.addButtonPressedListener(getSpawnButtonListener());
		content.addWidget(spawnButton);
		spawnButtons.put(pos, spawnButton);
		
		int halfSize = spawnButton.getWidth() / 2;
		int[] coords = getMapCoordinates(pos.getLocalTranslation());
		spawnButton.setXY(coords[0] - halfSize, coords[1] - halfSize);
	}
	
	private IButtonPressedListener getSpawnButtonListener() {
		return new IButtonPressedListener() {
			@Override
			public void buttonPressed(ButtonPressedEvent e) {
				SpawnButton sourceButton = (SpawnButton)e.getTrigger();
				setSpawn(sourceButton.getSpawnButtonContent());
			}
		};
	}
	
	private void setSpawn(SpawnButtonContent button) {
		if(selectedButton != null) selectedButton.setSelected(false);
		selectedButton = button;
		if(selectedButton == null) return;
		
		selectedButton.setSelected(true);
	}
	
	private void setAsteroidIcons() {
		asteroidIcons = new ArrayList<AsteroidIcon>();
		for(CollidableNode node : AsteroidField.allAsteroids.values()) {
			int[] coords = getMapCoordinates(node.getLocalTranslation());
			if(asteroidIconIsNear(coords)) continue;
			
			int halfSize = asteroidIconSize / 2;
			int x = coords[0] - halfSize;
			int y = coords[1] - halfSize;
			AsteroidIcon icon = new AsteroidIcon(x, y, asteroidIconSize, asteroidIconSize);
			asteroidIcons.add(icon);
			content.addWidget(icon);
		}
	}
	
	private boolean asteroidIconIsNear(int[] coords) {
		for(AsteroidIcon icon : asteroidIcons) {
			Vector2f v = new Vector2f(icon.getX() - coords[0], icon.getY() - coords[1]);
			if(v.length() < icon.getSize().getWidth() * 2 / 3) return true;
		}
		return false;
	}
	
	private int[] getMapCoordinates(Vector3f worldLocation) {
		float factorX = (float)content.getWidth() / (float)game.getMap().getDimension();
		float factorZ = (float)content.getHeight() / (float)game.getMap().getDimension();
		return new int[] { (int)(worldLocation.x * factorX), 
								(int)(content.getHeight() - worldLocation.z * factorZ) };
	}
	
	public SpawnPosition getSelectedSpawnLocation() {
		if(selectedButton == null) return null;
		return selectedButton.getSpawnPosition();
	}
	
	private void initPilotListeners() {
		addPlayerListeners(team);
		addPlayerListeners(enemyTeam);
	}
	
	private void addPlayerListeners(final Team team) {
		for(Player p : team.getAllPlayers()) {
			addPlayer(p);
			
			Hunter hunter = p.getHunter();
			if(hunter != null && hunter.getParent() != null) addShip(hunter); 
		}
		
		TeamListener teamListener = new TeamListener() {
			@Override
			public void playerRemoved(Player player) {
				Hunter hunter = player.getHunter();
				if(hunter != null) removeShip(hunter);
			}
			
			@Override
			public void playerAdded(Player p) {
				addPlayer(p);
				Hunter hunter = p.getHunter();
				if(hunter != null && hunter.getParent() != null) addShip(hunter); 
			}
			
			@Override
			public void teamValueChanged(TeamValueChangedEvent event) {}
			@Override
			public void droneDestroyed(DroneKillEvent event) {}
		};
		teamListeners.put(team, teamListener);
		spawnMenu.addListener(team, teamListener);
		
		TeamShipListener teamShipListener = new TeamShipListener() {
			@Override
			public void shipRemoved(ShipRemovedEvent event) {
				removeShip(event.getShip());
			}
			
			@Override
			public void shipAdded(ShipAddedEvent event) {
				addShip(event.getShip());
			}
		};
		teamShipListeners.put(team, teamShipListener);
		spawnMenu.addListener(team, teamShipListener);
	}
	
	private void addPlayer(Player player) {
		PlayerListener l = createPlayerListener(player);
		playerlisteners.put(player, l);
		spawnMenu.addListener(player, l);
		
		PlayerValueListener vl = createPlayerValueListener(player);
		playerValuelisteners.put(player, vl);
		spawnMenu.addListener(player, vl);
	}
	
	private PlayerListener createPlayerListener(final Player p) {
		return new PlayerListener() {
			@Override
			public void respawned(RespawnEvent event) {
				addShip(p.getHunter());
			}
			
			@Override
			public void hunterChanged(HunterChangedEvent event) {
				addShip(event.getNewHunter());
			}
			
			@Override
			public void targetChanged(TargetChangedEvent event) {}
			@Override
			public void killed(KillEvent event) {}
			@Override
			public void killAssist(KillAssistEvent event) {}
			@Override
			public void missionAssist(MissionAssistEvent event) {}
		};
	}
	
	private PlayerValueListener createPlayerValueListener(final Player p) {
		return new PlayerValueListener() {
			@Override
			public void valueChanged(StatusChangedEvent event) {
				if(event.getType() == ValueType.Deaths) {
					MiniMapIcon icon = shipIcons.get(p.getHunter());
					if(icon != null) {
						content.removeWidget(icon);
						if(icon instanceof SpawnButtonContent) {
							SpawnButtonContent spawnButtonIcon = (SpawnButtonContent)icon;
							if(spawnButtonIcon.isSelected()) setSpawn(null);
						}
					}
					visibleShips.remove(p.getHunter());
				}
			}
		};
	}
	
	private void addShip(MovableShip ship) {
		assert(ship != null);
		
		if(!shipIcons.containsKey(ship)) {
			MiniMapIcon icon = getShipIcon(ship);
			shipIcons.put(ship, icon);
		}
		
		if(!destructionListeners.containsKey(ship)) {
			DestructionListener sl = getNewDestructionListener(ship);
			destructionListeners.put(ship, sl);
			spawnMenu.addListener(ship, sl);
		}
		
		if(!visListeners.containsKey(ship)) {
			VisibilityListener vl = getVisibilityListener(ship);
			visListeners.put(ship, vl);
			spawnMenu.addListener(ship, vl);
		}
		
		changeVisiblity(ship, ship.getTeam() == team);
	}
	
	private void removeShip(MovableShip ship) {
		MiniMapIcon icon = shipIcons.remove(ship);
		
		if(icon != null) content.removeWidget(icon);
		
		visibleShips.remove(ship);
		destructionListeners.remove(ship);
		visListeners.remove(ship);
	}
	
	private DestructionListener getNewDestructionListener(final MovableShip ship) {
		return new DestructionListener() {
			@Override
			public void destroyed(DestructionEvent event) {
				changeVisiblity(ship, false);
			}
		};
	}
	
	private VisibilityListener getVisibilityListener(final MovableShip ship) {
		return new VisibilityListener() {
			@Override
			public void visibilityChanged(VisibilityChangedEvent event) {
				changeVisiblity(ship, ship.isVisibleToPlayer());
			}
		};
	}
	
	private void changeVisiblity(MovableShip ship, boolean vis) {
		MiniMapIcon icon = shipIcons.get(ship);
		if(icon == null) return;
		
		if(vis) {
			visibleShips.add(ship);
			content.addWidget(icon);
		} else {
			visibleShips.remove(ship);
			content.removeWidget(icon);
		}

		layout();
	}
	
	@Override
	public void changeHunter(Hunter hunter) {}
	
	@Override
	public void changeTeam(Team newTeam) {
		super.changeTeam(newTeam);
		
		shipIcons.clear();
		visibleShips.clear();
		
		for(MovableShip ship : destructionListeners.keySet()) {
			ship.removeDestructionListener(destructionListeners.get(ship));
		}
		destructionListeners.clear();
		
		for(MovableShip ship : visListeners.keySet()) {
			ship.removeVisListener(visListeners.get(ship));
		}
		visListeners.clear();
		
		for(Team team : teamListeners.keySet()) {
			team.removeTeamListener(teamListeners.get(team));
		}
		teamListeners.clear();
		
		for(Player player : playerlisteners.keySet()) {
			PlayerListener l = playerlisteners.get(player);
			if(l != null) player.removePlayerListener(l);
		}
		playerlisteners.clear();
		
		for(Player player : playerValuelisteners.keySet()) {
			PlayerValueListener l = playerValuelisteners.get(player);
			if(l != null) player.removePlayerValueListener(l);
		}
		playerValuelisteners.clear();
		
		selectedButton = null;
		for(SpawnButtonContent button : spawnButtons.values()) {
			button.clearStationListener();
			content.removeWidget(button);
		}
		spawnButtons.clear();
		
		initSpawnStations();
		initTeamSpawnPositions();
		
		setAsteroidIcons();
		initPilotListeners();
		
		initDefaultSpawn(true);
		
		layout();
	}
	
	@Override
	public void update(float time) {
		for(MovableShip ship : visibleShips) {
			MiniMapIcon icon = shipIcons.get(ship);
			assert(icon != null);
			
			int[] coords = getMapCoordinates(ship.getLocalTranslation());
			icon.setXY(coords[0], coords[1]);
		}
	}
}